package com.hpq.websocket.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.hpq.common.event.UserOnlineEvent;
import com.hpq.common.event.UserRegisterEvent;
import com.hpq.user.dao.UserDAO;
import com.hpq.user.domain.enums.RoleEnum;
import com.hpq.user.domain.model.IpInfo;
import com.hpq.user.domain.model.User;
import com.hpq.user.service.LoginService;
import com.hpq.user.service.RoleService;
import com.hpq.websocket.NettyUtil;
import com.hpq.websocket.domain.dto.WSChannelExtraDTO;
import com.hpq.websocket.domain.vo.response.WSBaseResp;
import com.hpq.websocket.service.WebSocketService;
import com.hpq.websocket.service.adapter.WebSocketAdapter;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@Service
public class WebSocketServiceImpl implements WebSocketService {
    @Autowired
    private WxMpService wxMpService;
    @Autowired
    UserDAO userDao;
    @Autowired
    LoginService loginService;
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    @Autowired
    private RoleService roleService;
    @Autowired
    private ThreadPoolTaskExecutor executor;
    /**
     * 管理所有用户的连接
     */
    private static final ConcurrentHashMap<Channel, WSChannelExtraDTO> ONLINE_WS_MAP = new ConcurrentHashMap<>();
    /**
     * 所有在线的用户和对应的socket
     */
    private static final ConcurrentHashMap<Long, CopyOnWriteArrayList<Channel>> ONLINE_UID_MAP = new ConcurrentHashMap<>();

    private static final Duration EXPIRE_TIME = Duration.ofHours(1);
    private static final Long MAX_MUM_SIZE = 10000L;
    /**
     * 临时保存所有请求登录的code与channel关系
     */
    public static final Cache<Integer, Channel> WAIT_LOGIN_MAP = Caffeine.newBuilder()
            .expireAfterWrite(EXPIRE_TIME)
            .maximumSize(MAX_MUM_SIZE)
            .build();
    public static ConcurrentHashMap<Channel, WSChannelExtraDTO> getOnlineMap() {
        return ONLINE_WS_MAP;
    }

    /**
     * 用户建立websocket连接
     *
     * @param ctx
     */
    @Override
    public void connect(Channel channel) {

        ONLINE_WS_MAP.put(channel, new WSChannelExtraDTO());

    }

    /**
     * 处理登录请求
     *
     * @param channel
     */
    @SneakyThrows
    @Override
    public void handlerLoginReq(Channel channel) {
        //生成随机的码
        Integer code = generateLoginCode(channel);
        //向微信申请登录二维码
        WxMpQrCodeTicket ticket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) EXPIRE_TIME.getSeconds());
        //将码推送给前端
        sendMsg(channel, WebSocketAdapter.buildResp(ticket));
    }

    @Override
    public void remove(Channel channel) {
        ONLINE_WS_MAP.remove(channel);
        // todo 用户下线
    }

    private void sendMsg(Channel channel, WSBaseResp<?> resp) {
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(resp)));
    }

    /**
     * 生成随机的码
     * @param channel
     * @return
     */
    private Integer generateLoginCode(Channel channel) {
        Integer code;
        //保证生成的登陆码唯一
        do{
            code = RandomUtil.randomInt(Integer.MAX_VALUE);
        }while(Objects.nonNull(WAIT_LOGIN_MAP.asMap().putIfAbsent(code,channel)));
        return code;
    }


    @Override
    public void  scanLoginSuccess(Integer loginCode, Long uid) {
        //确认连接在该机器
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(loginCode);
        if (Objects.isNull(channel)) {
            return ;
        }
        User user = userDao.getById(uid);
        //移除code
        WAIT_LOGIN_MAP.invalidate(loginCode);
        //调用用户登录模块
        String token = loginService.login(uid);
        //用户登录成功
        loginSuccess(channel,user,token);
        //sendMsg(channel, WebSocketAdapter.buildLoginResp(user,token));
    }
    @Override
    public void waitAuthorize(Integer code){
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
        if(Objects.isNull(channel)){
            return;
        }
        sendMsg(channel, WebSocketAdapter.buildWaitAuthorizeResp());
    }

    @Override
    public void authorize(Channel channel, String token) {
        Long uid = loginService.getValidUid(token);
        //判断token是否有效
        if(Objects.nonNull(uid)){
            //有效则登录
            User user = userDao.getById(uid);
            //登录成功
             loginSuccess(channel,user,token);
            //sendMsg(channel, WebSocketAdapter.buildLoginResp(user,token));

        }else{
            sendMsg(channel, WebSocketAdapter.buildInvalidTokenResp());
        }
    }

    /**
     *  群发消息给所有人
     * @param resp
     */
    @Override
    public void sendMsgToAll(WSBaseResp<?> resp) {
        ONLINE_WS_MAP.forEach((channel,extra)->{
            executor.execute(()->
                sendMsg(channel,resp));

        });
    }

    /**
     *  给某个用户发送消息
     * @param wsBaseMsg
     * @param uid
     */
    @Override
    public void sendToUid(WSBaseResp<?> wsBaseMsg, Long uid) {
        CopyOnWriteArrayList<Channel> channels = ONLINE_UID_MAP.get(uid);
        if(CollectionUtils.isEmpty(channels)){
            log.info("用户：{}不在线",uid);
            return;
        }
        channels.forEach(channel->{
            executor.execute(()->
                sendMsg(channel,wsBaseMsg));
        });
    }

    /**
     *  给所有在线用户发送消息
     * @param wsBaseMsg
     * @param uid
     */
    @Override
    public void sendToAllOnline(WSBaseResp<?> wsBaseMsg,Long uid) {
        ONLINE_WS_MAP.forEach((channel, extra) -> {
            if(Objects.nonNull(extra.getUid()) && extra.getUid().equals(uid)){
                return;
            }
            executor.execute(() -> {
                sendMsg(channel, wsBaseMsg);
            });
        });
    }

    @Override
    public Boolean scanSuccess(Integer code) {
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
        if (Objects.nonNull(channel)) {
            sendMsg(channel, WebSocketAdapter.buildScanSuccessResp());
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    private void loginSuccess(Channel channel, User user, String token) {
        //保存channel的uid
        ONLINE_WS_MAP.get(channel).setUid(user.getId());

        //登录成功消息推送
        sendMsg(channel, WebSocketAdapter.buildLoginResp(user,token,roleService.hasPower(user.getId(), RoleEnum.CHAT_MANAGER)));
        log.info("用户登录成功：{}", user);
        user.setLastOptTime(new Date());
        IpInfo info =  new IpInfo();
        info.setCreateIp(NettyUtil.getAttr(channel, NettyUtil.IP));
        user.setIpInfo(info);
        info.setUpdateIp(NettyUtil.getAttr(channel, NettyUtil.IP));
        //用户上线成功的事件
        applicationEventPublisher.publishEvent(new UserOnlineEvent(this,user));
    }

}
